#include "config.h"

#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>

#define DBG_SUBSYS S_LIBTASK

#include "cluster.h"
#include "chunk.h"
#include "lich_md.h"
#include "net_table.h"
#include "configure.h"
#include "../replica/replica.h"
#include "disk.h"
#include "../../storage/controller/pool_ctl.h"
#include "../../storage/controller/volume_ctl.h"
#include "../../storage/controller/stor_ctl.h"
#include "../../storage/controller/md_proto.h"
#include "nodectl.h"
#include "recovery.h"
#include "types.h"
#include "token_bucket.h"
#include "dbg.h"
#include "utils.h"
#include "system.h"
#include "sysutil.h"

extern hashtable_t recovery_tab;

#if 0

typedef struct {
        sy_rwlock_t lock;
        uint64_t success;
} recovery_persistent_t;


static recovery_persistent_t *__recovery_persisent = NULL;


int __set_disk_recovery_total(uint64_t recovery_total)
{
        char path[MAX_NAME_LEN];
        char value[MAX_BUF_LEN];

        memset (path, 0, MAX_NAME_LEN);
        sprintf(path, "recovery/success_total");

        sprintf(value, "%ju", recovery_total);
        opt_config_set(path, value);

        return 0;
}

static uint64_t __get_disk_recovery_total()
{
        char path[MAX_PATH_LEN];

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, "recovery/success_total");

        return recovery_status_get_long(path, 0);
}

int recovery_persistent_init()
{
        int ret;

        if (__recovery_persisent == NULL) {
                ret = ymalloc((void **)&__recovery_persisent, sizeof(recovery_persistent_t));
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = sy_rwlock_init(&__recovery_persisent->lock, "__r_persist");
                if (unlikely(ret))
                        GOTO(err_free, ret);

                __recovery_persisent->success = __get_disk_recovery_total();
        }

        return 0;
err_free:
        yfree((void **)&__recovery_persisent);
err_ret:
        return ret;
}

int recovery_persistent_add_success(uint64_t success)
{
        int ret;

        YASSERT(__recovery_persisent != NULL);

        ret = sy_rwlock_wrlock(&__recovery_persisent->lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        __recovery_persisent->success += success;

        sy_rwlock_unlock(&__recovery_persisent->lock);
        return 0;
err_ret:
        return ret;
}

#endif

int set_recovery_total(char *pool_name, uint64_t recovery_total, int is_disk)
{
        char path[MAX_NAME_LEN];
        char value[MAX_BUF_LEN];

        memset(path, 0, MAX_NAME_LEN);
        if (is_disk) {
                sprintf(path, RECOVERY"%s"RECOVERY_DISK_RECOVERY_TOTAL, pool_name);
        } else {
                sprintf(path, RECOVERY"%s"RECOVERY_RECOVERY_TOTAL, pool_name);
        }

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, path);

        sprintf(value, "%ju", recovery_total);
        pnf->set(pnf, value);

        return 0;
}

uint64_t get_recovery_total(char *pool_name, int is_disk)
{
        char path[MAX_PATH_LEN];
        uint64_t recovery_total;

        memset(path, 0, MAX_PATH_LEN);
        if (is_disk) {
                sprintf(path, RECOVERY"%s"RECOVERY_DISK_RECOVERY_TOTAL, pool_name);
        } else {
                sprintf(path, RECOVERY"%s"RECOVERY_RECOVERY_TOTAL, pool_name);
        }

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, path);

        recovery_total = pnf->get_long(pnf, 0);
        return recovery_total;
}

int _set_thread(recovery_pool_t *pool, int thread_num)
{
        char path[MAX_NAME_LEN];
        char value[MAX_BUF_LEN];

        // ask fill rate
        memset (path, 0, MAX_NAME_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_THREAD, pool->name);

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, path);

        sprintf(value, "%d", thread_num);
        pnf->set(pnf, value);

        return pool->config.thread;
}

int common_load_thread(const char *pool, int is_disk)
{
        char path[MAX_PATH_LEN];
        int thread;

        // ask fill rate
        memset (path, 0, MAX_PATH_LEN);
        if (is_disk) {
                sprintf(path, "recovery/%s/disk_thread", pool);
        } else {
                sprintf(path, "recovery/%s/thread", pool);
        }

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, path);

        thread = pnf->get_int(pnf, RECOVERY_THREAD_MAX);
        if (thread <= 0)
                thread = 1;

        YASSERT(thread > 0);
        return thread;
}

int load_thread(recovery_pool_t *pool)
{
        int thread = common_load_thread(pool->name, 0);
        if (thread > RECOVERY_THREAD_MAX) {
                DWARN("pool_name:%s thread %d max thread %d", pool->name, thread, RECOVERY_THREAD_MAX);
                thread = RECOVERY_THREAD_MAX;
        }

        if (thread <= 0) {
                thread = 10;
        }

        pool->config.thread = thread;
        return thread;
}

int common_load_fill_rate(const char *pool, token_bucket_t *tb, int is_disk)
{
        int fill_rate;
        char path[MAX_PATH_LEN];

        memset(path, 0, MAX_PATH_LEN);
        if (is_disk) {
                sprintf(path, "recovery/%s/disk_fill_rate", pool);
        } else {
                sprintf(path, "recovery/%s/fill_rate", pool);
        }

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, path);

        fill_rate = pnf->get_int(pnf, RECOVERY_FILL_RATE_DEFAULT_VAL);
        if (fill_rate < 0)
                fill_rate = 0;

        if (tb) {
                token_bucket_set(tb, (is_disk?"diskmd_recovery":"recovery"), fill_rate, fill_rate, fill_rate, 0, 0);
        }

        return fill_rate;
}

int load_fill_rate(recovery_pool_t *pool)
{
        pool->config.fill_rate = common_load_fill_rate(pool->name, &pool->token_bucket, 0);
        return pool->config.fill_rate;
}

int recovery_load_scale(const char *pool)
{
        int ret;
        char path[MAX_PATH_LEN];

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, "recovery/%s/scale", pool);

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, path);

        ret = pnf->get_int(pnf, 50);

        if (ret <= 0)
                ret = 1;
        if (ret >= 100)
                ret = 99;

        return ret;
}

int recovery_load_task_max(const char *pool)
{
        int ret, task_min = 0, task_max = 128, task_def = 0;
        char path[MAX_PATH_LEN];

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, "recovery/%s/task_max", pool);

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, path);

        ret = pnf->get_int(pnf, task_def);

        if (ret < task_min)
                ret = task_min;
        if (ret > task_max)
                ret = task_max;

        return ret;
}

void recovery_config_dump(recovery_pool_t *pool)
{
        char path[MAX_PATH_LEN];

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_THREAD, pool->name);

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, path);

        pool->config.thread = pnf->get_int(pnf, RECOVERY_THREAD_NUMBER);
        if (pool->config.thread <= 0)
                pool->config.thread = 1;

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_FILL_RATE, pool->name);

        opt_data_init(pnf, path);
        pool->config.fill_rate = pnf->get_int(pnf, RECOVERY_FILL_RATE_DEFAULT_VAL);
}

int recovery_delete_all_pool_file(recovery_pool_t *pool)
{
        char path[MAX_PATH_LEN];

        recovery_immediately_unregister(pool->name);

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, "/opt/fusionstack/data/"RECOVERY"%s", pool->name);
        _delete_path(path);

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, "/dev/shm/lich4/nodectl/"RECOVERY"%s", pool->name);
        _delete_path(path);

        return 0;
}

#if 0

static int __is_recovery_on(recovery_pool_t *pool)
{
        int ret;
        char path[MAX_PATH_LEN];

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_IMMEDIATELY, pool->name);

        ret = nodectl_get_int(path, 0);
        return ret == 0 ? 0 : 1;
}

void recovery_set_off(recovery_pool_t *pool)
{
        int ret;
        char path[MAX_PATH_LEN];

        if (__is_recovery_on(pool)) {
                memset (path, 0, MAX_PATH_LEN);
                sprintf(path, RECOVERY"%s"RECOVERY_IMMEDIATELY, pool->name);
                ret = nodectl_set(path, "0");
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);
        }
}

#endif

static int __recovery_immediately(void *context, uint32_t mask)
{
        recovery_pool_t *pool = context;
        char path[MAX_PATH_LEN];

        (void) mask;

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_IMMEDIATELY, pool->name);

        int on = nodectl_get_int(path, 0);
        if (on == 1) {
                DINFO("wakeup pool %s status %d type: immediately\n",
                      pool->name, pool->status);

                if (pool->status == __RECOVERY_WAITING__) {
                        sem_post(&pool->sem);
                }

                nodectl_set2(path, "0\n");
        }

        return 0;
}

static int __recovery_immediately_reset(void *context, uint32_t mask)
{
        int ret;
        recovery_pool_t *pool = context;
        char path[MAX_PATH_LEN];

        (void) mask;

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_IMMEDIATELY, pool->name);

        ret = nodectl_register(path, "0", __recovery_immediately, __recovery_immediately_reset, context);
        if (unlikely(ret))
                UNIMPLEMENTED(__DUMP__);

        return 0;
}

int recovery_immediately(recovery_pool_t *pool)
{
        int ret;
        char path[MAX_PATH_LEN];

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_IMMEDIATELY, pool->name);

        ret = nodectl_register(path, "0", __recovery_immediately, __recovery_immediately_reset, pool);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int recovery_load_interval()
{
        YASSERT(gloconf.recovery_interval > 0);
        return gloconf.recovery_interval;
}

int recovery_wakeup_one_pool(void *pool_name, void *_arg)
{
        recovery_pool_t *pool;
        const char *ctx = (const char *)_arg;

        if ((recovery_tab == NULL) || (pool_name == NULL)) {
                return 0;
        }

        pool = hash_table_find(recovery_tab, (void *)pool_name);
        if (pool) {
                if (pool->status == __RECOVERY_WAITING__) {
                        DINFO("wakeup pool %s status %d removing %d ctx %s\n",
                              pool->name, pool->status, pool->status_remove, ctx);
                        sem_post(&pool->sem);
                }
        }

        return 0;
}

int recovery_wakeup_all_pool(int type)
{
        int ret;

        DINFO("recovery wakeup type: %d\n", type);

        ret = system_pool_iterator(recovery_wakeup_one_pool, "all");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int recovery_immediately_unregister(const char *pool)
{
        int ret;
        char path[MAX_PATH_LEN];

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_IMMEDIATELY, pool);

        ret = nodectl_unregister(path);
        if (unlikely(ret)) {
                DWARN("nodectl_unregister failed path %s ret %d\n", path, ret);
                // GOTO(err_ret, ret);
        }

        sprintf(path, "recovery/%s/commit_max", pool);
        ret = nodectl_unregister(path);
        if (unlikely(ret)) {
                DWARN("nodectl_unregister failed path %s ret %d\n", path, ret);
                // GOTO(err_ret, ret);
        }

        sprintf(path, "recovery/%s/disk_switch", pool);
        ret = nodectl_unregister(path);
        if (unlikely(ret)) {
                DWARN("nodectl_unregister failed path %s ret %d\n", path, ret);
                // GOTO(err_ret, ret);
        }

        return 0;
}

static int __get_recovery_qos_mode()
{
        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, RECOVERY_QOS_MODE);

        return pnf->get_int(pnf, 0);
}

static int __recovery_qos_mode(void *context, uint32_t mask)
{
        (void) context;
        (void) mask;
        int mode;

        mode = __get_recovery_qos_mode();

        recovery_qos_set_mode(mode);

        return 0;
}

static int __recovery_qos_mode_reset(void *context, uint32_t mask)
{
        int ret;

        (void) context;
        (void) mask;

        ret = opt_config_register(RECOVERY_QOS_MODE, "0", __recovery_qos_mode, __recovery_qos_mode_reset, NULL);
        if (unlikely(ret))
                UNIMPLEMENTED(__DUMP__);

        return 0;
}

int recovery_config_init() {
        // return recovery_register(RECOVERY_QOS_MODE, "0", __recovery_qos_mode, __recovery_qos_mode_reset, NULL);
        return 0;
}

int recovery_node_get(recovery_pool_t *pool)
{
        int ret;
        char path[MAX_PATH_LEN];

        memset (path, 0, MAX_PATH_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_NODE_ONLINE, pool->name);

        ret = nodectl_get_int(path, 0);
        return ret == 0 ? 0 : 1;
}

void recovery_node_offline_set(recovery_pool_t *pool)
{
        char path[MAX_PATH_LEN];

        if (recovery_node_get(pool)) {
                memset (path, 0, MAX_PATH_LEN);
                sprintf(path, RECOVERY"%s"RECOVERY_NODE_ONLINE, pool->name);
                nodectl_set(path, "0");
        }
}


static void recovery_node_online_set(recovery_pool_t *pool)
{
        char path[MAX_PATH_LEN];

        if (!recovery_node_get(pool)) {
                memset (path, 0, MAX_PATH_LEN);
                sprintf(path, RECOVERY"%s"RECOVERY_NODE_ONLINE, pool->name);
                nodectl_set(path, "1");
        }
}

static int recovery_node_online_one_pool(void *pool_name, void *_arg)
{
        recovery_pool_t *pool;
        (void) _arg;

        if ((recovery_tab == NULL) || (pool_name == NULL)){
                return 0;
        }

        pool = hash_table_find(recovery_tab, (void *)pool_name);
        if (pool) {
                recovery_node_online_set(pool);
        }

        return 0;
}

int recovery_node_online_all_pool()
{
        int ret;

        ret = system_pool_iterator(recovery_node_online_one_pool, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}
